﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Mbs.Container.Metadata;

namespace Mbs.Container.Interceptor
{
    /// <summary>
    /// New interface that is going to be used by DynamicProxy 2
    /// </summary>
    public interface IInterceptor
    {
        void Intercept(IInvocation invocation);
    }

    [Serializable]
    public class StandardInterceptor : MarshalByRefObject, IInterceptor
	{
		public void Intercept(IInvocation invocation)
		{
			PreProceed(invocation);
			PerformProceed(invocation);
			PostProceed (invocation);
		}

		protected virtual void PerformProceed(IInvocation invocation)
		{
			invocation.Proceed ();
		}

		protected virtual void PreProceed(IInvocation invocation)
		{
		}

		protected virtual void PostProceed(IInvocation invocation)
		{
		}
	}


    /// <summary>
    /// Provides an extension point that allows proxies to choose specific interceptors on
    /// a per method basis.
    /// </summary>
    public interface IInterceptorSelector
    {
        /// <summary>
        /// Selects the interceptors that should intercept calls to the given <paramref name="method"/>.
        /// </summary>
        /// <param name="type">The type declaring the method to intercept.</param>
        /// <param name="method">The method that will be intercepted.</param>
        /// <param name="interceptors">All interceptors registered with the proxy.</param>
        /// <returns>An array of interceptors to invoke upon calling the <paramref name="method"/>.</returns>
        /// <remarks>
        /// This method is called only once per proxy instance, upon the first call to the
        /// <paramref name="method"/>. Either an empty array or null are valid return values to indicate
        /// that no interceptor should intercept calls to the method. Although it is not advised, it is
        /// legal to return other <see cref="IInterceptor"/> implementations than these provided in
        /// <paramref name="interceptors"/>.
        /// </remarks>
        IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors);
    }

    /// <summary>
    /// Exposes means to change target objects of proxies and invocations
    /// </summary>
    public interface IChangeProxyTarget
    {
        /// <summary>
        /// Changes the target object (<see cref="IInvocation.InvocationTarget"/>) of current <see cref="IInvocation"/>.
        /// </summary>
        /// <param name="target">The new value of target of invocation.</param>
        /// <remarks>
        /// Although the method takes <see cref="object"/> the actual instance must be of type assignable to <see cref="IInvocation.TargetType"/>, otherwise an <see cref="System.InvalidCastException"/> will be thrown.
        /// Also while it's technically legal to pass null reference (Nothing in Visual Basic) as <paramref name="target"/>, for obvious reasons Dynamic Proxy will not be able to call the intercepted method on such target.
        /// In this case last interceptor in the pipeline mustn't call <see cref="IInvocation.Proceed"/> or a <see cref="System.NotImplementedException"/> will be throws.
        /// Also while it's technically legal to pass proxy itself as <paramref name="target"/>, this would create stack overflow.
        /// In this case last interceptor in the pipeline mustn't call <see cref="IInvocation.Proceed"/> or a <see cref="System.InvalidOperationException"/> will be throws.
        /// </remarks>
        /// <exception cref="System.InvalidCastException">Thrown when <paramref name="target"/> is not assignable to the proxied type.</exception>
        void ChangeInvocationTarget(object target);


        /// <summary>
        /// Permanently changes the target object of the proxy. This does not affect target of the current invocation.
        /// </summary>
        /// <param name="target">The new value of target of the proxy.</param>
        /// <remarks>
        /// Although the method takes <see cref="object"/> the actual instance must be of type assignable to proxy's target type, otherwise an <see cref="System.InvalidCastException"/> will be thrown.
        /// Also while it's technically legal to pass null reference (Nothing in Visual Basic) as <paramref name="target"/>, for obvious reasons Dynamic Proxy will not be able to call the intercepted method on such target.
        /// In this case last interceptor in the pipeline mustn't call <see cref="IInvocation.Proceed"/> or a <see cref="System.NotImplementedException"/> will be throws.
        /// Also while it's technically legal to pass proxy itself as <paramref name="target"/>, this would create stack overflow.
        /// In this case last interceptor in the pipeline mustn't call <see cref="IInvocation.Proceed"/> or a <see cref="System.InvalidOperationException"/> will be throws.
        /// </remarks>
        /// <exception cref="System.InvalidCastException">Thrown when <paramref name="target"/> is not assignable to the proxied type.</exception>
        void ChangeProxyTarget(object target);
    }

    /// <summary>
    /// Interceptors might implement this to receive the
    /// ComponentModel on behalf of the component where the
    /// interceptor is acting.
    /// </summary>
    public interface IOnBehalfAware
    {
        void SetInterceptedComponent(IComponentInfo target);
    }

    public interface IProxyTargetAccessor
    {
        /// <summary>
        /// Get the proxy target (note that null is a valid target!)
        /// </summary>
        /// <returns></returns>
        object DynProxyGetTarget();

        /// <summary>
        /// Gets the interceptors for the proxy
        /// </summary>
        /// <returns></returns>
        IInterceptor[] GetInterceptors();
    }

    /// <summary>
    /// Encapsulates an invocation of a proxied method.
    /// </summary>
    public interface IInvocation
    {
        /// <summary>
        /// Gets the proxy object on which the intercepted method is invoked.
        /// </summary>
        /// <value>Proxy object on which the intercepted method is invoked.</value>
        object Proxy { get; }

        /// <summary>
        /// Gets the object on which the invocation is performed. This is different from proxy object
        /// because most of the time this will be the proxy target object.
        /// </summary>
        /// <seealso cref="IChangeProxyTarget"/>
        /// <value>The invocation target.</value>
        object InvocationTarget { get; }

        /// <summary>
        /// Gets the type of the target object for the intercepted method.
        /// </summary>
        /// <value>The type of the target object.</value>
        Type TargetType { get; }

        /// <summary>
        /// Gets the arguments that the <see cref="Method"/> has been invoked with.
        /// </summary>
        /// <value>The arguments the method was invoked with.</value>
        object[] Arguments { get; }

        /// <summary>
        /// Overrides the value of an argument at the given <paramref name="index"/> with the
        /// new <paramref name="value"/> provided.
        /// </summary>
        /// <remarks>
        /// This method accepts an <see cref="object"/>, however the value provided must be compatible
        /// with the type of the argument defined on the method, otherwise an exception will be thrown.
        /// </remarks>
        /// <param name="index">The index of the argument to override.</param>
        /// <param name="value">The new value for the argument.</param>
        void SetArgumentValue(int index, object value);

        /// <summary>
        /// Gets the value of the argument at the specified <paramref name="index"/>.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns>The value of the argument at the specified <paramref name="index"/>.</returns>
        object GetArgumentValue(int index);

        /// <summary>
        /// Gets the generic arguments of the method.
        /// </summary>
        /// <value>The generic arguments, or null if not a generic method.</value>
        Type[] GenericArguments { get; }

        /// <summary>
        /// Gets the <see cref="MethodInfo"/> representing the method being invoked.
        /// </summary>
        /// <value>The <see cref="MethodInfo"/> representing the method being invoked.</value>
        MethodInfo Method { get; }

        /// <summary>
        /// Returns the concrete instantiation of the <see cref="Method"/>, with any generic
        /// parameters bound to real types.
        /// </summary>
        /// <returns>
        /// The concrete instantiation of the <see cref="Method"/>, or the <see cref="Method"/> if
        /// not a generic method.
        /// </returns>
        /// <remarks>Can be slower than calling <see cref="Method"/>.</remarks>
        MethodInfo GetConcreteMethod();

        /// <summary>
        /// For interface proxies, this will point to the <see cref="MethodInfo"/> on the target class.
        /// </summary>
        /// <value>The method invocation target.</value>
        MethodInfo MethodInvocationTarget { get; }

        /// <summary>
        /// Returns the concrete instantiation of <see cref="MethodInvocationTarget"/>, with any
        /// generic parameters bound to real types.
        /// </summary>
        /// <returns>The concrete instantiation of <see cref="MethodInvocationTarget"/>, or
        /// <see cref="MethodInvocationTarget"/> if not a generic method.</returns>
        /// <remarks>Can be slower than calling <see cref="MethodInvocationTarget"/>.</remarks>
        MethodInfo GetConcreteMethodInvocationTarget();

        /// <summary>
        /// Gets or sets the return value of the method.
        /// </summary>
        /// <value>The return value of the method.</value>
        object ReturnValue { get; set; }

        /// <summary>
        /// Proceeds the call to the next interceptor in line, and ultimately to the target method.
        /// </summary>
        /// <remarks>
        /// Since interface proxies without a target don't have the target implementation to proceed to,
        /// it is important, that the last interceptor does not call this method, otherwise a
        /// <see cref="NotImplementedException"/> will be thrown.
        /// </remarks>
        void Proceed();
    }
}
